package mysql

import (
	"database/sql"
	"strconv"
)

type MysqlResult struct {
	Rows          *sql.Rows
	Columns       []string
	FieldsIndex   map[string]int
	FieldsData    []sql.NullString
	fieldsDataPtr []interface{}
}

type DataTable struct {
	Columns     []string
	RowData     [][]string
	FieldsIndex map[string]int
	RowIndex    int
}

func NewMysqlResult(rows *sql.Rows) *MysqlResult {
	res := &MysqlResult{
		Rows:        rows,
		FieldsIndex: make(map[string]int),
	}

	columns, err := res.Rows.Columns()
	if err == nil {
		res.Columns = columns
		for idx, col := range columns {
			res.FieldsIndex[col] = idx
		}

		res.FieldsData = make([]sql.NullString, len(columns))
		res.fieldsDataPtr = make([]interface{}, len(columns))
		for i := 0; i < len(res.FieldsData); i++ {
			res.fieldsDataPtr[i] = &res.FieldsData[i]
		}
	}
	return res
}

func NewDataTable(columns []string, rowData [][]string) *DataTable {
	table := &DataTable{
		Columns:     columns,
		RowData:     rowData,
		FieldsIndex: make(map[string]int),
		RowIndex:    -1,
	}

	for idx, field := range table.Columns {
		table.FieldsIndex[field] = idx
	}
	return table
}

func (res *MysqlResult) Close() {
	if res.Rows != nil {
		_ = res.Rows.Close()
		res.Rows = nil
	}
}

func (res *MysqlResult) Next() (bool, error) {
	ok := res.Rows.Next()
	if !ok {
		return false, nil
	}

	err := res.Rows.Scan(res.fieldsDataPtr...)
	if err != nil {
		return false, err
	}
	return true, nil
}

func (res *MysqlResult) IsNull(field string) bool {
	if idx, ok := res.FieldsIndex[field]; ok {
		return !res.FieldsData[idx].Valid
	}
	return true
}

func (res *MysqlResult) Get(field string) string {
	if idx, ok := res.FieldsIndex[field]; ok {
		return res.FieldsData[idx].String
	}
	return ""
}

func (res *MysqlResult) GetInt8(field string) int8 {
	return int8(res.GetInt64(field))
}

func (res *MysqlResult) GetUInt8(field string) uint8 {
	return uint8(res.GetUInt64(field))
}

func (res *MysqlResult) GetInt16(field string) int16 {
	return int16(res.GetInt64(field))
}

func (res *MysqlResult) GetUInt16(field string) uint16 {
	return uint16(res.GetUInt64(field))
}

func (res *MysqlResult) GetInt32(field string) int32 {
	return int32(res.GetInt64(field))
}

func (res *MysqlResult) GetUInt32(field string) uint32 {
	return uint32(res.GetUInt64(field))
}

func (res *MysqlResult) GetInt64(field string) int64 {
	v, _ := strconv.ParseInt(res.Get(field), 10, 64)
	return v
}

func (res *MysqlResult) GetUInt64(field string) uint64 {
	v, _ := strconv.ParseUint(res.Get(field), 10, 64)
	return v
}

func (res *MysqlResult) GetFloat32(field string) float32 {
	return float32(res.GetFloat64(field))
}

func (res *MysqlResult) GetFloat64(field string) float64 {
	v, _ := strconv.ParseFloat(res.Get(field), 64)
	return v
}

func (res *MysqlResult) FetchAll() ([][]string, error) {
	var rowData [][]string
	for {
		ok, err := res.Next()
		if err != nil {
			return nil, err
		}
		if !ok {
			break
		}

		row := make([]string, len(res.FieldsData))
		for i := 0; i < len(row); i++ {
			row[i] = res.FieldsData[i].String
		}
		rowData = append(rowData, row)
	}
	return rowData, nil
}

func (res *MysqlResult) ConvertDataTable() (*DataTable, error) {
	rowData, err := res.FetchAll()
	if err != nil {
		return nil, err
	}
	return NewDataTable(res.Columns, rowData), nil
}

func (tbl *DataTable) Next() bool {
	if tbl.RowIndex+1 >= len(tbl.RowData) {
		return false
	}
	tbl.RowIndex++
	return true
}

func (tbl *DataTable) Get(field string) string {
	if idx, ok := tbl.FieldsIndex[field]; ok {
		return tbl.RowData[tbl.RowIndex][idx]
	}
	return ""
}

func (tbl *DataTable) GetInt8(field string) int8 {
	return int8(tbl.GetInt64(field))
}

func (tbl *DataTable) GetUInt8(field string) uint8 {
	return uint8(tbl.GetUInt64(field))
}

func (tbl *DataTable) GetInt16(field string) int16 {
	return int16(tbl.GetInt64(field))
}

func (tbl *DataTable) GetUInt16(field string) uint16 {
	return uint16(tbl.GetUInt64(field))
}

func (tbl *DataTable) GetInt32(field string) int32 {
	return int32(tbl.GetInt64(field))
}

func (tbl *DataTable) GetUInt32(field string) uint32 {
	return uint32(tbl.GetUInt64(field))
}

func (tbl *DataTable) GetInt64(field string) int64 {
	v, _ := strconv.ParseInt(tbl.Get(field), 10, 64)
	return v
}

func (tbl *DataTable) GetUInt64(field string) uint64 {
	v, _ := strconv.ParseUint(tbl.Get(field), 10, 64)
	return v
}

func (tbl *DataTable) GetFloat32(field string) float32 {
	return float32(tbl.GetFloat64(field))
}

func (tbl *DataTable) GetFloat64(field string) float64 {
	v, _ := strconv.ParseFloat(tbl.Get(field), 64)
	return v
}
